package com.gitee.wsl.io.buffer

import com.gitee.wsl.lang.ByteOrder
import kotlin.math.min

/**
 * Port of a subset of Java's ByteBuffer with full Endian support. This class should be replaceable
 * by an equivalent class in kotlinx-io once that library settles. This ByteBuffer is
 * implemented on a ByteArray for simplicity
 */
abstract class ByteBufferBase<Element, Array> constructor(
    capacity: Int,
    order: ByteOrder = ByteOrder.LittleEndian,
    override val isReadOnly: Boolean = false
) : BufferAble<Element, Array>(-1, 0, capacity, capacity, order),
    Comparable<ByteBufferBase<Element, Array>> {

    abstract var buf: Array
        protected set

    private var offset = 0
    val contentBytes get() = buf

    /**
     * The following properties offer simple encode/decode operations as indicated by the ByteOrder
     * currently in effect. Properties are defined for many basic types
     */

    /**
     * This property offers byte-level read/write. get returns the byte at the current position,
     * and increments then position by one. set changes the byte at the current position, then
     * increments the position by one.
     *
     * @throws IllegalStateException if the position is already at the limit
     */
    override var byte: Element
        get() {
            checkPosition()
            return getElementAt(position++)
        }
        set(value) {
            checkPosition()
            setElementAt(position++, value)
        }

    /**
     * Compacts this buffer&nbsp;&nbsp;<i>(optional operation)</i>.
     *
     * <p> The bytes between the buffer's current position and its limit,
     * if any, are copied to the beginning of the buffer.  That is, the
     * byte at index <i>p</i>&nbsp;=&nbsp;<tt>position()</tt> is copied
     * to index zero, the byte at index <i>p</i>&nbsp;+&nbsp;1 is copied
     * to index one, and so forth until the byte at index
     * <tt>limit()</tt>&nbsp;-&nbsp;1 is copied to index
     * <i>n</i>&nbsp;=&nbsp;<tt>limit()</tt>&nbsp;-&nbsp;<tt>1</tt>&nbsp;-&nbsp;<i>p</i>.
     * The buffer's position is then set to <i>n+1</i> and its limit is set to
     * its capacity.  The mark, if defined, is discarded.
     *
     * <p> The buffer's position is set to the number of bytes copied,
     * rather than to zero, so that an invocation of this method can be
     * followed immediately by an invocation of another relative <i>put</i>
     * method. </p>
     *

     *
     * <p> Invoke this method after writing data from a buffer in case the
     * write was incomplete.  The following loop, for example, copies bytes
     * from one channel to another via the buffer <tt>buf</tt>:
     *
     * <blockquote><pre>{@code
     *   buf.clear();          // Prepare buffer for use
     *   while (in.read(buf) >= 0 || buf.position != 0) {
     *       buf.flip();
     *       out.write(buf);
     *       buf.compact();    // In case of partial write
     *   }
     * }</pre></blockquote>
     *
     * @return This buffer
     */
    fun compact() {
        var destIndex = 0
        for (i in position until position + limit) {
            setElementAt(destIndex++, getElementAt(i))
        }
        position = destIndex
        limit = capacity
        resetMark()
    }

    /**
     * Compares this buffer to another.
     *
     * <p> Two byte buffers are compared by comparing their sequences of
     * remaining elements lexicographically, without regard to the starting
     * position of each sequence within its corresponding buffer.
     *
     * @return A negative integer, zero, or a positive integer as this buffer
     *          is less than, equal to, or greater than the given buffer
     */
    override fun compareTo(other: ByteBufferBase<Element, Array>): Int {
        val n: Int = position + min(remaining, other.remaining)
        var i: Int = position
        var j: Int = other.position
        while (i < n) {
            val r = compareElement(getElementAt(i), other.getElementAt(j))
            if (r != 0) return r
            i++
            j++
        }
        return 0
    }

    abstract fun compareElement(element: Element, other: Element): Int

    /**
     * The contents of the current buffer are replaced with the contents of the source.  Position,
     * limit, capacity, order, and mark will all be set to same as the source.
     */
    fun copy(source: ByteBufferBase<Element, Array>) {
        buf = source.buf
        position = source.position
        limit = source.limit
        mark = source.mark
        order = source.order
    }

    /**
     * Similar to [ByteArray] copyInto
     * @param destination Array to receive copy
     * @param destinationOffset index into destination where copy will start
     * @param startIndex index in source array where copy will start
     * @param endIndex index in source that copy will end, exclusive. [endIndex - startIndex] will
     * be number of bytes copied.
     */
    abstract fun copyInto(
        destination: Array,
        destinationOffset: Int = 0,
        startIndex: Int = 0,
        endIndex: Int
    )

    /**
     * Starting at the current position, copy bytes into the specified destination array. Increment
     * position by the number of bytes retrieved.
     *
     * @param destination
     *      The array with sufficient capacity into which bytes will be written
     * @param destinationOffset
     *      The offset into the destination at which the copy will start
     * @param length
     *      The number of bytes to be copied
     * @return This buffer
     */
    fun fillArray(
        destination: Array,
        destinationOffset: Int = 0,
        length: Int
    ) {
        checkBounds(destinationOffset, length, length)
        if (length > remaining)
            throw IllegalStateException("Copying length:$length is more than remaining:$remaining")
        copyInto(destination, destinationOffset, position, position + length)
        position += length
    }

    /**
     * <p> This method transfers the bytes remaining in the given source
     * buffer into this buffer.  If there are more bytes remaining in the
     * source buffer than in this buffer, that is, if
     * <tt>src.remaining()</tt>&nbsp;<tt>&gt;</tt>&nbsp;<tt>remaining()</tt>,
     * then no bytes are transferred and an IllegalStateException is thrown
     * </p>
     *
     * <p> Otherwise, this method copies
     * <i>n</i>&nbsp;=&nbsp;<tt>src.remaining()</tt> bytes from the given
     * buffer into this buffer, starting at each buffer's current position.
     * The positions of both buffers are then incremented by <i>n</i>.
     */
    open fun put(source: ByteBufferBase<Element, Array>) {
        if (source == this)
            throw IllegalArgumentException("Cannot copy ByteBuffer to itself")
        val sourceBytes = source.remaining
        if (sourceBytes > remaining)
            throw IllegalArgumentException("Remaining source bytes:$sourceBytes exceeds destination remaining:$remaining")

        source.fillArray(buf, position, sourceBytes)
        position += sourceBytes
    }

    /**
     * The following are all private functions
     */
    private fun checkPosition(forLength: Int = 1) {
        if (position + forLength > limit)
            throw IllegalStateException()
    }
}